Skip to content

feat: say what wikven does about Lua, and stop guessing at it - #473

Merged
lens0021 merged 3 commits into
mainfrom
claude/lua-modules
Aug 19, 2026
Merged

feat: say what wikven does about Lua, and stop guessing at it#473
lens0021 merged 3 commits into
mainfrom
claude/lua-modules

Conversation

@lens0021

@lens0021 lens0021 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Scribunto is ordinary equipment on a MediaWiki wiki, and wikven's two products disagreed about it silently. Measured on a bake of one page invoking one module, every one of these exited 0:

Scribunto module file rendered module in the export
listed, engine present Module:Example Lua says hello no
not listed Module:Example {{#invoke:Greet|hello}} no, the file was ignored entirely
not listed Module:Example.wikitext {{#invoke:Greet|hello}} yes, as Module%3AGreet.html

Three of those four ways to have a Lua module published a site with braces where the page meant to say something, and one exported the module's own Lua source as a page. Nothing said a word. #465 guessed this would show up as "red module errors"; raw wikitext is worse, because it looks like someone's typo rather than a missing engine.

The answer, per the decision on #465

Supported under the Docker image, not in the standalone binary. That is not a preference. static-php-cli, which builds the binary's PHP, offers no Lua extension among the ~130 it supports, and Scribunto's other engine shells out to a lua program that a single executable has no way to ship. Checked against upstream's own list rather than assumed.

One refusal and one warning

includes/Scribunto.php holds both rules; build.php runs them before clearOutputDirectory, so a site that is refused still has its last bake on disk.

  • Scribunto where no engine can run it → refused, naming the product that cannot. The site asked for Lua in so many words and this build cannot give it; publishing braces instead is worse than being told no.
  • Module: files with no Scribunto in extensions → warned about, naming the files, and the bake goes on. The site never asked for Lua, and what those files are is a guess read off a name: they may be on their way in, on their way out, or kept for something else. Refusing there decides for the site, which is not this build's call. So it says what it sees — the invocations stay in the pages as their own source text, and a module named with the .wikitext marker is exported as a page — and lets the bake finish.

A site with no Lua at all is told nothing, which matters: that is most sites, and the binary is what they are pointed at.

Verified by baking, not by reading. The refusal:

=== scribunto-with-engine
  bake exit: 0
  index.html: present
=== no-lua-at-all
  bake exit: 0
  index.html: present

The engine-missing case was tested by mounting an empty ini over the image's docker-php-ext-luasandbox.ini, which is as close to the binary as this machine gets:

bake exit: 1
Wikven: this site lists Scribunto and no Lua engine is available here. The Docker image has one;
the standalone binary has none, so it cannot bake a site that uses Lua modules. ...

That run also proves the image has no lua on PATH: engine detection tries luasandbox, then a configured luaPath, then lua5.1/lua on PATH, and it still fired. A hand-rolled install with the standalone engine is therefore not refused for no reason.

The modules-without-Scribunto case was baked while it was still a refusal (bake exit: 1, no index.html, message naming Module:Greet). Since it became a warning it is covered by the unit suite and not by a fresh bake — the bake host here has no Docker. What changed is which of the two functions produces the string and whether build.php calls fatalError() or output() with it; the message and the file detection are the same code that bake exercised.

The page

Lua modules, between Extensions and JavaScript, in English and Korean. How to enable it, why a module file carries no .wikitext marker (its content model is Lua), what reaches the published site, and what each of the two mismatches does.

It demonstrates itself: it shows Module:Example in full, invokes it, and prints the answer. The module returns the title of the page it ran on and no prose, because a module's return value is not a translation unit and would reach a reader of any language in whatever language it was written in — the sentence around it carries the words instead.

Standalone binary gains a "What it cannot do" section: it claimed its output was the same as the image's, which is now known to be false in exactly one way. Extensions points at the new page. Development documents the new build step.

What holds it

smoke.yml asserts the module's answer is in the exported page in both languages, and that nothing named Module* was exported.

It deliberately does not assert the absence of {{#invoke: in the output. The new page documents the syntax inside <syntaxhighlight> and so contains it legitimately — confirmed in the bake, three occurrences. The positive assertion is the one that tells rendering from leaking.

Ran locally against a real bake of this site:

ok    dist/Lua_modules.html carries the module's answer
ok    dist/Lua_modules/ko.html carries it in Korean
ok    the module is not a page of the site
ok    414 language-bar entries, as the source predicts

Two bakes byte-identical, so a module costs no reproducibility. The prevnext chain reads Skins → Extensions → Lua modules → JavaScript → Searching, and shows Lua 모듈 in Korean, which incidentally exercises #466 on a brand-new page. translate check reports everything up to date.

A consequence worth stating plainly

This documentation site now has a Lua module, so the standalone binary cannot bake it. Nothing in CI notices, because nothing bakes anything with the binary: binary.yml builds it and runs ./wikven version. That gap is real, it is not this change's to close, and it is worth its own issue.

Closes #465.

Scribunto is ordinary equipment on a MediaWiki wiki, and wikven's two products disagreed about it silently. Measured on a bake of one page invoking one module, every one of these exited 0:

| Scribunto | module file | rendered | module in the export |
|---|---|---|---|
| listed, engine present | `Module:Example` | `Lua says hello` | no |
| not listed | `Module:Example` | `{{#invoke:Greet|hello}}` | no, the file was ignored entirely |
| not listed | `Module:Example.wikitext` | `{{#invoke:Greet|hello}}` | **yes, as `Module%3AGreet.html`** |

So three of four ways to have a Lua module produced a published site with braces where the page meant to say something, and one of them exported the module's own Lua source as a page. Nothing said a word.

## The answer

Scribunto is supported under the Docker image and not in the standalone binary. That is not a preference: static-php-cli, which builds the binary's PHP, offers no Lua extension among the hundred and thirty it supports, and Scribunto's other engine shells out to a `lua` program that a single executable has no way to ship. Checked upstream rather than assumed.

## Two refusals

`Scribunto` holds the rule, and `build.php` puts it before `clearOutputDirectory` so a site that is refused still has its last bake on disk.

* a site with `Module:` files and no `Scribunto` in `extensions` is refused, naming the files;
* a site that lists Scribunto where no engine can run it is refused, naming the product that cannot.

Verified by baking, not by reading. Modules without Scribunto now exit 1 with no output written. Scribunto with no engine was tested by mounting an empty ini over the image's `docker-php-ext-luasandbox.ini`, which is as close to the binary as this machine gets, and it exits 1 with the message about the binary. A site with Scribunto and an engine bakes as before; a site with no Lua at all is told nothing, which matters because that is most sites and the binary is what they are pointed at.

Engine detection is `luasandbox`, then a configured `luaPath`, then `lua5.1` or `lua` on PATH, so a hand-rolled install with the standalone engine is not refused for no reason. The image has no `lua` on PATH, which the test above also proves: the check fired.

## The page

`Lua modules`, in the docs section between Extensions and JavaScript, in English and Korean. It says how to enable it, that a module file carries no `.wikitext` marker because its content model is Lua, what reaches the published site, and what the two refusals look like. `Standalone binary` gains a "What it cannot do" section, since it claimed its output was the same as the image's, and that is now known to be false in one way. `Extensions` points at the new page.

The page demonstrates itself: it shows `Module:Example` in full, invokes it, and prints the answer. The module returns the title of the page it ran on and no prose, because a module's return value is not a translation unit and would reach a reader of any language in whatever language it was written in.

## What holds it

`smoke.yml` asserts the module's answer is in the exported page in both languages, and that nothing named `Module*` was exported. It cannot assert the absence of `{{#invoke:` anywhere, because the new page documents the syntax inside `<nowiki>` and so contains it legitimately -- the positive assertion is the one that distinguishes rendering from leaking.

Ran locally against a real bake of this site: both languages carry the answer, `<code>Lua modules</code>` and `<code>Lua modules/ko</code>`, the module is not a page, the language-bar count still matches what the source predicts (414), and two bakes are byte-identical, so a module does not cost reproducibility. The prevnext chain reads Skins, Extensions, Lua modules, JavaScript, Searching in English, and shows `Lua 모듈` in Korean.

Unit suite: `OK (196 tests, 299 assertions)` against main, eleven of them new.

## A consequence worth stating

This documentation site now has a Lua module, so the standalone binary cannot bake it. Nothing in CI notices, because nothing bakes anything with the binary -- `binary.yml` builds it and runs `./wikven version`. That gap is real and is not this change's to close.

Closes #465.

---
_Generated by [Claude Code](https://claude.ai/code/session_935f02d1)_

Co-authored-by: Claude <noreply@anthropic.com>
claude added 2 commits August 19, 2026 15:51
A source tree with Module: files and no Scribunto in extensions ended the
build. That is deciding for the site: what those files are is a guess read
off a name, and they may be on their way in, on their way out, or kept for
something else. The bake now goes on and says what it sees -- the
invocations stay in the pages as their own source text, and a module named
with the .wikitext marker is exported as a page.

The other rule stays fatal, because it is not the same thing. A site that
lists Scribunto where no engine can run it asked for Lua in so many words,
and this build cannot give it; publishing braces instead is worse than
being told no.

So Scribunto::problem() narrows to the engine case and a new warning()
carries the modules case, and build.php's check is no longer only an
assertion.
@github-actions
github-actions Bot temporarily deployed to preview August 19, 2026 15:54 Destroyed
@lens0021
lens0021 merged commit ea10b7d into main Aug 19, 2026
24 checks passed
@lens0021
lens0021 deleted the claude/lua-modules branch August 19, 2026 15:56
lens0021 added a commit that referenced this pull request Aug 20, 2026
… that found (#481)

Nothing had ever baked anything with the standalone binary. `binary.yml`
built it and ran `./wikven version` — which proves the static link,
since static linking fails at execution rather than compilation, but
proves nothing about the product — and `smoke.yml` only ever uses the
Docker image. So a site the image can bake and the binary cannot had
never been caught by anything, which is how this repository's own
documentation came to hold a Lua module the binary was documented as
unable to render.

This adds the missing job, fixes the three bugs it found on its first
runs, and rewrites the documentation those runs proved wrong. Closes
#465's remaining gap; the follow-up #473 flagged.

## The job

A `bake` job hanging off `binary` with `needs:`, so it runs exactly when
a binary was built. Not folded into `smoke.yml`: that job belongs to the
image, it is a required check, and Lua is a niche of a niche. It runs on
a plain runner with the binary downloaded as an artifact — the way
somebody who fetched a release meets it, with no Docker and nothing of
wikven's installed.

Three assertions, in this order, because the order is load-bearing:

| | asserts | where |
|---|---|---|
| a bake with no `src` | a refused bake exits non-zero | both |
| Scribunto's bundled lua, nothing configured, **before anything is
installed** | the out-of-the-box claim | x86_64 |
| `luaPath` at an interpreter the host provides | the arrangement that
works anywhere | both |

Putting the bundled-lua bake after the install step made it pass for the
wrong reason on the first run: `/usr/bin/lua5.1` was on `PATH`, which is
one of the things `luaEngineAvailable()` looks for, so the bake was
never asked whether it can manage without one.

A pull request builds x86_64 only; the arm64 leg runs on the nightly and
on releases, which is where an arm binary exists at all. It was
exercised for this branch on the throwaway #482.

## What it found

**1. A relative `WIKVEN_WORKDIR` broke the skin pass.** The first bake
ever run got through the import and the job queue and died:

```
--- vector-2022 pass failed (exit 1) ---
DBQueryError ... Error 1: no such table: page
Query: SELECT MIN(page_id) FROM "page"
#9 .../extensions/Wikven/maintenance/build.php(842): RebuildFileCache->execute()
```

A skin pass is spawned with the MediaWiki root as its working directory
— `proc_open(..., $GLOBALS['IP'], ...)`, because the binary's php-cli
needs script paths relative to the install root — and inherits
`WIKVEN_WORKDIR`. Inside the binary that root is the temporary directory
the embedded app is unpacked into, so a relative value means one place
in the orchestrator and another in every pass: the pass opened an empty
database beside the unpacked app instead of the one just filled. Not a
corner case — it is what `binary.Dockerfile`'s own usage note documents,
`WIKVEN_WORKDIR=. ./wikven build`. Under Docker the workdir is
`/workspace`, absolute, which is why it never showed up. Resolved with
`realpath()`.

**2. The engine check refused sites it could have baked.**
`luaEngineAvailable()` looked for `luasandbox`, a configured `luaPath`,
and `lua` on `PATH`, and never for the interpreter Scribunto carries in
its own source tree — which is what `luastandalone` runs when `luaPath`
is null, and what `luaautodetect` falls back to when `luasandbox` is
absent. It looks for it now, and **runs** it rather than trusting
`is_executable()`: `LuaStandaloneInterpreter` picks among the bundled
binaries by `PHP_OS` and `PHP_INT_SIZE` and never by architecture, so on
arm the file it selects is an x86-64 ELF that exists, is executable, and
cannot start. Executing it is what makes mirroring that choice safe — a
wrong guess answers no rather than promising Lua that never arrives.

**3. Every failed build exited 0.** Caught by the assertion added here,
on both architectures:

```
wikven: no source directory at /home/runner/work/wikven/wikven/empty/src
a bake with no source directory exited 0
```

`reexec()` returned the child's exit code with a nil error, meaning to
propagate the status without Caddy printing its own line over the
child's diagnostic. Caddy 2.11.4, `cmd/cobra.go`:

```go
status, err := f(Flags{cmd.Flags()})
if status > 1 {
    cmd.SilenceErrors = true
    return &exitError{ExitCode: status, Err: err}
}
return err
```

A status of exactly 1 is never turned into an exit status, and an
ordinary failed build is 1. So a Makefile, a CI job or a deploy script
running `wikven build` read every refusal as a success — the Lua
refusal, a missing source directory, a failed skin pass, all of them.
`reexec()` exits instead of returning; returning `(1, err)` would exit 1
but print Caddy's error line over the diagnostic the child already
wrote, which is what the original nil was avoiding.

## The documentation

`Lua modules` and `Standalone binary` both said the binary cannot render
Lua. Written before anything had baked with it, and wrong in two cases
of three:

| Product | Engine | What you configure |
|---|---|---|
| Docker image | `luasandbox`, compiled into its PHP | nothing |
| Standalone binary, x86-64 | the interpreter Scribunto ships | nothing
|
| Standalone binary, arm64 | an interpreter you install | `luaPath` |

Every row is measured rather than reasoned. `lua5.1 5.1.5-9build2`
installs from `universe` on the arm runner, no `luajit` fallback needed,
and the bake with `luaPath` pointed at it finished with the module's
answer in the page. arm64 with nothing installed refuses cleanly — no
output written, and now a non-zero status.

`Standalone binary`'s "What it cannot do" becomes "What it asks of the
host", which is what the section now describes. Korean follows,
restamped.

## Not done here

`binary` is still 504s and still off the critical path of nothing, which
is #479's problem, not this one's.

---------

Co-authored-by: Claude <noreply@anthropic.com>
lens0021 added a commit that referenced this pull request Aug 20, 2026
Closes #455.

## The duplication

The order of the documentation was written down twice: as
`MediaWiki:Sidebar`, and again as the arguments of the `{{prevnext}}` at
the foot of each of twenty-one pages. Nothing checked that the two
agreed, and twice they did not — a page reached the sidebar with no call
naming it, and the last page of the chain had no row at all, because a
lone argument read as the *next* step and no positional form could spell
"nothing after this".

They agree today, as it happens: twenty sidebar entries, and the chain
covers all twenty in the sidebar's order with `index` at the head. That
is the state to keep, not a state to rely on — both past divergences
were repaired by hand, and the shape that produced them was untouched.

## The change

`Module:Sequence` reads the sidebar, so `{{prevnext}}` takes no
arguments. Adding a page is now two edits instead of three, and the
third was the invisible one.

The direction is forced, as #455 worked out: `MediaWiki:Sidebar` is read
line by line by the skin rather than parsed as wikitext, so it can call
nothing; but it is an ordinary page, so anything can read it.

What the sidebar contributes is **order alone**. Labels stay in
`Template:prevnext/label`, which reads each target's own translated
title — so none of this reaches the translations. A prevnext sits
outside the `<translate>` tags, and no `ko.wikitext` changed in this
branch.

## The two decisions #455 left open

* **The group boundary.** The sequence keeps crossing from the docs
group into the references group, as it does today. A reader who reaches
the end of the first group is better sent on than stopped.
* **`index`.** It heads the sequence without being a sidebar entry, so
the module prepends it. The alternative — a sidebar entry for it — would
duplicate what every skin's logo already points at, which is why the
sidebar's own comment says it has none.

## The condition #455 set

> **check first that it runs in the image**: Scribunto needs either the
LuaSandbox PHP extension or a standalone `lua` binary, and neither is
obviously present in the Alpine-based image. If it is missing, this
proposal costs a Dockerfile change, and that changes the trade.

It runs. The base image does `pecl install LuaSandbox-4.1.2`, and this
site has rendered `Module:Example` since #473. **No Dockerfile change**,
which is what tips this over the alternative — #455's cheaper option was
a check that asserts the two copies agree, and a check that watches a
duplication is worth less than not having the duplication, once the
module is free.

## What holds it

`smoke` gets what the old shape got wrong, stated as three assertions:
every page the sidebar names has a navigation row, `index` has no
previous link, and `Licenses` has no next. The existing check that
`Searching/ko`'s row carries Translating's *Korean* title still stands
and now also proves the module renders at all.

Draft because the module cannot be run here — no Docker on this machine,
so the first real execution is CI's bake.


---
_Generated by [Claude
Code](https://claude.ai/code/session_01Uv1RzRurUH6wrgV5E9PESQ)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
lens0021 pushed a commit that referenced this pull request Aug 31, 2026
🤖 I have created a release *beep* *boop*
---


## 1.0.0 (2026-08-31)

The first release. There is no earlier version to compare against or
upgrade from, so
what follows is not a list of changes to one but the work this release
is made of, as
the commits recorded it.


### Features

* **action:** default to a pinned image version and cache it between
runs ([#188](#188))
([f7fcb40](f7fcb40))
* **actions:** offer a guiding pull request comment on translation
findings ([#520](#520))
([1ab0175](1ab0175))
* add a "View source" tab linking to the repo source
([#119](#119))
([24e4d16](24e4d16))
* add a {{WIKVENVERSION}} variable
([#604](#604))
([a702b9f](a702b9f))
* add a footer skin switcher
([#114](#114))
([1867ea6](1867ea6))
* add a mark command to number translation units
([#216](#216))
([0fcde73](0fcde73))
* add a serve subcommand for local preview
([#155](#155))
([8d97799](8d97799))
* add translate scaffold to generate translation skeletons
([#223](#223))
([48ce2ab](48ce2ab))
* bake ULS webfonts into the static export (opt-in)
([#286](#286))
([074a103](074a103))
* bake with the standalone binary in CI, and fix the three things that
found ([#481](#481))
([8cf3bc3](8cf3bc3))
* base the image on mediawiki:1.46-fpm-alpine
([#332](#332))
([f3fdcee](f3fdcee))
* **build:** list the site's extensions and skins, with their licenses
([#523](#523))
([42d75d2](42d75d2))
* **build:** name wikven in the User-Agent of every request it makes
([#542](#542))
([7d6d1ce](7d6d1ce))
* bundle the native search closure so SifterSearch search works offline
([#98](#98))
([cf6b910](cf6b910)),
closes [#97](#97)
* derive page files from content model, dropping the .css.wikitext
suffix ([#62](#62))
([ef645c4](ef645c4))
* derive the footer project link label from the repo host
([#118](#118))
([e14c059](e14c059))
* **docs:** give the landing page symbols, an output it can show, and
some warmth
([#524](#524))
([3b07995](3b07995))
* **docs:** give the note template a teal icon
([#341](#341))
([05a7ea2](05a7ea2))
* **docs:** keep the rendered page behind the tree instead of trading
places ([#530](#530))
([83fc9ee](83fc9ee))
* **docs:** put a copy button on the code blocks
([#377](#377))
([3beee91](3beee91))
* **docs:** stop the sidebar linking to the page you are reading
([#499](#499))
([1bf9e2b](1bf9e2b))
* **docs:** turn the diagram's output pane over on scroll where the
browser can
([#526](#526))
([6c7bcc8](6c7bcc8))
* fold the version page into an About page
([#426](#426))
([af61267](af61267))
* gate translation checks on broken source pages, not on stale
translations
([#385](#385))
([aacfbfc](aacfbfc))
* generate a Special:Version-style page linked from the footer
([#117](#117))
([d2dfa8a](d2dfa8a))
* give Minerva a main menu the export can serve
([#366](#366))
([fc59117](fc59117))
* give the standalone binary the translate commands
([#507](#507))
([25ba28e](25ba28e))
* **i18n:** translate the footer, skin switcher and version page strings
([#123](#123))
([9849b36](9849b36))
* index each language separately, and assert it stays that way
([#447](#447))
([b4b01cc](b4b01cc))
* keep non-main skin pages out of search indexes
([#113](#113))
([0d8d94e](0d8d94e))
* keep Vector's appearance menu, which works statically
([#387](#387))
([ce30fea](ce30fea))
* let a site choose the skin it is read in
([#589](#589))
([5f96e9e](5f96e9e))
* let a skin author turn off the chrome wikven imposes
([#551](#551))
([ea78cab](ea78cab))
* let a stamp mean someone read the translation
([#572](#572))
([12d3292](12d3292))
* load .wikven.yaml through MediaWiki's settings system and validate it
([#77](#77))
([0952b3b](0952b3b))
* localize translated-page chrome and route Special:Translate links to
the edit host
([#229](#229))
([3afa7c4](3afa7c4))
* make page titles translatable, and translate them into Korean
([#256](#256))
([acdd975](acdd975))
* make the Translating page translatable
([#248](#248))
([6122151](6122151))
* name a setting that belongs to no one
([#578](#578))
([3df5b0e](3df5b0e))
* name the setting core cannot accept
([#574](#574))
([56f2d80](56f2d80))
* offer the Citizen skin (search hidden)
([#115](#115))
([b83717b](b83717b))
* pin and verify fetched extensions/skins
([#74](#74))
([8ccd5e7](8ccd5e7))
* point non-main skin pages' canonical at the main copy
([#116](#116))
([5bf1d6a](5bf1d6a))
* publish images on releases and nightlies, not on every commit to main
([#488](#488))
([18fa018](18fa018))
* publish the image to quay.io as well, with no token to keep
([#485](#485))
([f387e5e](f387e5e))
* put everything the build generates in one directory
([#584](#584))
([d8b8f43](d8b8f43))
* put the generated templates under one name, and keep the old ones
([#559](#559))
([2cd766a](2cd766a))
* put the pictures pages carry where the rest of the output goes
([#592](#592))
([78fdf9f](78fdf9f))
* put the skin switcher in the toolbox and drop the footer select
([#351](#351))
([2f2551e](2f2551e))
* render each enabled skin into its own output directory
([#111](#111))
([b0adc94](b0adc94))
* require MediaWiki 1.46, dropping 1.45 support
([#236](#236))
([993e62c](993e62c))
* resolve Special:MyLanguage/ links in the static export
([#225](#225))
([f4904ea](f4904ea))
* say what every built site redistributes, on a page the footer links
([#560](#560))
([3fe4eb6](3fe4eb6))
* say what wikven does about Lua, and stop guessing at it
([#473](#473))
([ea10b7d](ea10b7d))
* select the build skin via WIKVEN_BUILD_SKIN
([#110](#110))
([126a021](126a021))
* serve logos from a shared upload instead of inlining them per page
([#56](#56))
([ad4017a](ad4017a))
* serve the search results at Search and exclude it from indexing
([#104](#104))
([85d35e2](85d35e2))
* settle the actions' inputs before 1.0.0 freezes them
([#583](#583))
([2bee0c1](2bee0c1))
* settle what a page is called in the output
([#605](#605))
([a2860f7](a2860f7))
* ship a composite action and dogfood it in the docs deploy
([#150](#150))
([19392b7](19392b7))
* ship SifterSearch as a built-in, on-by-default search extension
([#195](#195))
([7b19be8](7b19be8))
* stop the build on three things it used to publish around
([#590](#590))
([9c0a0b3](9c0a0b3))
* support Citizen in wikven instead of per-site workarounds
([#369](#369))
([bced0dd](bced0dd))
* translate page content into other languages
([#214](#214))
([28ff2b2](28ff2b2))
* use the core viewsource message for the View source tab
([#120](#120))
([24230c0](24230c0))


### Bugfixes

* abort the build when a page fails to import
([#146](#146))
([cf0a007](cf0a007))
* **action:** use a locally-built image instead of always pulling
([#194](#194))
([8046aa4](8046aa4))
* answer symlinks where a source tree is read, not where a path is
bounded ([#591](#591))
([d3be13e](d3be13e))
* build the image when a Translate dev dependency has an advisory
([#277](#277))
([00838b9](00838b9))
* build the search index once, at the end
([#283](#283))
([09a5324](09a5324))
* **build:** put wikven in front of the User-Agent core builds for
Commons ([#543](#543))
([4c774a3](4c774a3))
* bump bundled SifterSearch to v0.6.0 (stop the double search box)
([#200](#200))
([cd38aa7](cd38aa7))
* bundle the modules core loads by looking at the rendered page
([#484](#484))
([c96c107](c96c107))
* **check-translations:** keep the comment to what the change touches
([#541](#541))
([8288229](8288229))
* clear the output directory before each build
([#72](#72))
([b4cf416](b4cf416))
* clear the title cache once the translation units exist
([#466](#466))
([562ea9e](562ea9e))
* create directories with wfMkdirParents
([#289](#289))
([5157174](5157174))
* date each page at the commit that changed it, not at the bake
([#424](#424))
([4cd31f4](4cd31f4)),
closes [#406](#406)
* declare the build's directory settings in extension.json
([#262](#262))
([65432b5](65432b5))
* decode JS-bundle URL escapes with json_decode in AssetLocalizer
([#297](#297))
([ccbf702](ccbf702))
* **docs:** cross the output pane's two states gradually instead of at a
line ([#540](#540))
([85fb9c9](85fb9c9))
* **docs:** cut the hero's light at the screen, and turn the pane over
mid-screen ([#531](#531))
([ea482af](ea482af))
* **docs:** keep sidebar links in the reader's language
([#382](#382))
([f2b27f4](f2b27f4))
* **docs:** keep the hero's light inside the hero
([#525](#525))
([aaeef7a](aaeef7a))
* **docs:** keep the logo in the reader's language
([#391](#391))
([749f507](749f507))
* **docs:** let the hero's light run off the frame so it reads as ground
([#529](#529))
([3d40660](3d40660))
* **docs:** let the hero's light run past the text column again
([#539](#539))
([2fbee0e](2fbee0e))
* **docs:** let the output pane settle back on the page as the diagram
leaves ([#544](#544))
([9d0785a](9d0785a))
* **docs:** light the hero from its corners, and drop the clip
[#531](#531) needed
([#536](#536))
([186421c](186421c))
* **docs:** make the brand teal and the note box legible in dark mode
([#489](#489))
([a03a76c](a03a76c))
* **docs:** make the hero's light read as ground rather than as two
spheres ([#528](#528))
([e31f7a5](e31f7a5))
* **docs:** paint the landing page's marks on its translations, and its
buttons anywhere
([#498](#498))
([1ff3a50](1ff3a50))
* **docs:** stop the diagram clipping its own file names on a narrow
screen ([#527](#527))
([74c35fb](74c35fb))
* **docs:** translate the Minerva section of Skins into Korean
([#415](#415))
([2ac133a](2ac133a))
* don't treat a documented &lt;translate&gt; example as a translation
source ([#239](#239))
([cdcc388](cdcc388))
* drain translation jobs by hand again, reverting
[#311](#311)
([#326](#326))
([4170fd7](4170fd7))
* drop dead footer/search affordances from the static export
([#160](#160))
([c08a5d2](c08a5d2))
* drop the .git the bundled extension clones leave in the image
([#330](#330))
([d19dbf4](d19dbf4))
* drop the category footer's dead Special:Categories link
([#173](#173))
([2df6bf8](2df6bf8))
* drop the discussion tab in every skin, Minerva included
([#493](#493))
([cd0fccd](cd0fccd))
* dump ResourceLoader modules without load.php's HTTP response
([#259](#259))
([3a39aac](3a39aac))
* end a source translation unit where its &lt;translate&gt; block ends
([#379](#379))
([50aebd8](50aebd8))
* export subpage titles as real subdirectories
([#213](#213))
([1db8e0d](1db8e0d))
* fail on un-localized images and hand output back to the host user
([#161](#161))
([1bb8c86](1bb8c86))
* fail the build when a stylesheet never reaches the disk
([#565](#565))
([6ad1b41](6ad1b41))
* fail the build when the webfonts a site asked for cannot be copied
([#566](#566))
([6fbd87a](6fbd87a))
* fail when a composer package lands where nothing loads it
([#581](#581))
([263fee5](263fee5))
* fetch a third-party extension again when its pin moves
([#546](#546))
([44938c2](44938c2))
* fetch the bundled extensions as tarballs, not clones
([#331](#331))
([9f60efa](9f60efa))
* freeze page_touched so wiki-page modules hash the same every bake
([#271](#271))
([853c06f](853c06f))
* give a fetch that reaches the network more than one go
([#469](#469))
([ba9fb1d](ba9fb1d))
* give Citizen's "View source" tab an icon
([#440](#440))
([f0b4e02](f0b4e02))
* give Citizen's search shortcuts the search the export has
([#397](#397))
([27eeebf](27eeebf))
* give every source image its own File: page
([#603](#603))
([623b866](623b866))
* give the per-skin build its own environment and working directory
([#298](#298))
([cfb0243](cfb0243))
* give the skin list its own section instead of renaming the toolbox
([#363](#363))
([aa13b7b](aa13b7b))
* grant the release binary job the provenance permissions it needs
([#177](#177))
([be9f283](be9f283))
* hide dead skin chrome left by the static export in Timeless and
Citizen ([#196](#196))
([1b4c9e1](1b4c9e1))
* hide edit/history/source links on generated pages
([#130](#130))
([fb6df1e](fb6df1e))
* hide the category footer on the static export
([#121](#121))
([939c435](939c435))
* hide the empty tools box each skin draws around the toolbox
([#346](#346))
([5afb909](5afb909))
* index a translated page once, not once per title it has
([#458](#458))
([e934e00](e934e00))
* keep $wgCacheEpoch out of module versions
([#270](#270))
([7795bf9](7795bf9))
* keep a search inside the skin copy the reader is in
([#434](#434))
([32e7f6d](32e7f6d))
* keep a skin pass out of the other skins' pages
([#430](#430))
([e84cd11](e84cd11))
* keep the build's chrome off a licenses page the site wrote
([#568](#568))
([e45596e](e45596e))
* keep the paths the build derives out of a site's hands
([#573](#573))
([e21b16b](e21b16b))
* keep the site's own styles linked from a style directory
([#563](#563))
([5dfea21](5dfea21))
* leave a page of its own where a translation would go
([#597](#597))
([09934b7](09934b7))
* link the search toggle to the results page, not to Special:Search
([#421](#421))
([3b3b2d5](3b3b2d5))
* list directories with FilesystemIterator, not glob
([#294](#294))
([f8188fa](f8188fa))
* make edit/history links correct for all valid titles
([950c0b2](950c0b2)),
closes [#67](#67)
* make storeImages downloads status-aware and stream to disk
([#307](#307))
([b75afc3](b75afc3))
* make the exported HTML the same in every bake
([#282](#282))
([caf6661](caf6661))
* make the first listed skin the default skin
([#109](#109))
([19e3fae](19e3fae))
* make the main page configurable and fail if it was not imported
([#71](#71))
([4399b8f](4399b8f))
* make verbatimRanges() comment-aware
([#312](#312))
([59117f7](59117f7))
* match &lt;translate&gt; with its attributes, as Translate does
([#292](#292))
([243bbb0](243bbb0))
* move Vector's skin list into the appearance menu
([#423](#423))
([68917e0](68917e0)),
closes [#405](#405)
* name the project namespace Wikven instead of clashing with MediaWiki
([#105](#105))
([4695978](4695978))
* never read a translation file as a base page
([#390](#390))
([6adb270](6adb270))
* obtain GadgetRepo from the service, not the removed singleton()
([288c733](288c733))
* point prevnext navigation at the reader's-language pages
([#245](#245))
([a3c8411](a3c8411))
* probe openssl's own compiled-in CA bundle location first
([#309](#309))
([c82d5b1](c82d5b1))
* propagate the child's exit code from wikven build and serve
([#302](#302))
([5c75797](5c75797))
* read a source page the way Translate segments it
([#388](#388))
([ff95548](ff95548))
* read a subpage as a translation only where it says it is one
([#580](#580))
([e967fb5](e967fb5))
* read units the way Translate does
([#358](#358))
([4642fc8](4642fc8))
* read Vector feature classes through classList
([#301](#301))
([9dfebd1](9dfebd1))
* rebase the printfooter link on a page exported into a subdirectory
([#422](#422))
([5e5b905](5e5b905))
* refuse an extension or skin name that points outside the image
([#570](#570))
([35c0678](35c0678))
* refuse an image or asset path that climbs out of its directory
([#588](#588))
([8fe34a1](8fe34a1))
* refuse images the source tree only points at
([#596](#596))
([38669f5](38669f5))
* render every translated page after all pages are marked
([#243](#243))
([0d987d3](0d987d3))
* reparent the local URLs a page carries in its JavaScript config
([#445](#445))
([beba746](beba746))
* replace fetchExtensions' curl subprocess with core's HTTP client
([#308](#308))
([9be62ec](9be62ec))
* replace hand-rolled balanced-div matcher with RemexHtml
([#314](#314))
([3173c2d](3173c2d))
* report a vetoed File:/MediaWiki: import as a failure
([#291](#291))
([8140261](8140261))
* require real tag context before rewriting a relative reference
([#313](#313))
([0f04ab6](0f04ab6))
* require UniversalLanguageSelector explicitly and disable its webfonts
([#218](#218))
([1b3e1b7](1b3e1b7))
* resolve $wgWikvenLogos through Title/RepoGroup, not by hand
([#315](#315))
([6f4de14](6f4de14))
* resolve phan findings and target MediaWiki 1.45
([be87a48](be87a48))
* resolve translate mark/stamp file paths against the source directory
([#220](#220))
([0aa95ea](0aa95ea))
* retry and cache the build's Wikimedia Commons lookups
([#257](#257))
([2ac2cce](2ac2cce))
* run the job queue in a fixed order
([#281](#281))
([6aa694e](6aa694e))
* say what language the licenses page's copies are in
([#562](#562))
([4242977](4242977))
* send a Special:MyLanguage link to the file it means
([#611](#611))
([1aad459](1aad459))
* set the maintenance user without StubGlobalUser
([8128dc5](8128dc5)),
closes [#93](#93)
* settle the search bundle's language order between bakes
([#451](#451))
([e7fc96c](e7fc96c))
* show the search box when SifterSearch provides static search
([#99](#99))
([a9cd689](a9cd689))
* small build-script and config papercuts
([#76](#76))
([1302e23](1302e23))
* split translation units only on adjacent newlines, as Translate does
([#295](#295))
([0feeebf](0feeebf))
* stop a new unit inheriting a deleted one's translations
([#564](#564))
([5fdd228](5fdd228))
* stop suppressing real categories; drop Version __NOINDEX__
([#122](#122))
([a3686bf](a3686bf))
* stop the build when an image of yours did not import
([#569](#569))
([c78b572](c78b572))
* stop the RLPAGEMODULES rewrite from editing article prose
([#296](#296))
([9ef6b8a](9ef6b8a))
* stop three things going quiet
([#599](#599))
([9103f0c](9103f0c))
* stop ULS reaching for input methods the export cannot serve
([#402](#402))
([af60e0e](af60e0e))
* strip the clocks ImageMagick writes into exported thumbnails
([#276](#276))
([a1bb8d0](a1bb8d0))
* switch skins correctly from a translated page
([#258](#258))
([6f85fe6](6f85fe6))
* take the DISPLAYTITLE spellings from the magic word registry
([#293](#293))
([7c88a7a](7c88a7a))
* take the translations comment down rather than turn it into an
all-clear ([#595](#595))
([6b88a10](6b88a10))
* **tf:** skip the vulnerability-alerts import when the resource is
absent ([#265](#265))
([b5d3ba3](b5d3ba3))
* translate a page whose file name is not spelled like its title
([#567](#567))
([6ec596a](6ec596a))
* turn the parser cache off so a bake reads its own finished state
([#335](#335))
([91036c3](91036c3))
* use CSSMin's data: URI encoder in AssetLocalizer
([#310](#310))
([6fe63d5](6fe63d5))
* validate .wikven.yaml value shapes and flag warnings clearly
([#163](#163))
([021f819](021f819))
* wrap a word too long for the screen instead of scrolling the page
([#545](#545))
([818a0e9](818a0e9))


### Performance

* freeze page_touched once, in the orchestrator
([#431](#431))
([a2d878b](a2d878b))
* render the skins beside each other
([#437](#437))
([33bbbfb](33bbbfb))
* stop rendering the history pages the export throws away
([#432](#432))
([39fe30c](39fe30c))


### Refactoring

* group the composite actions under actions/
([#359](#359))
([01f0f3d](01f0f3d))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scribunto works under Docker and not in the standalone binary, and the docs say nothing about it

2 participants